package Parser;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;

/**
 * So, Run is a class that will load the input file and arrange everything 
 * so that Parser can run smoothly.  This involves expanding rules, removing 
 * left recursion, etc.
 * 
 * @author Lee
 * @version 1.0
 */
public class Run {
	final static private int ARGC = 2; 
	private BufferedReader in;
	private String[] nonTerminalsList;
	private String start;
	private ArrayList<Rule> rulesList = new ArrayList<Rule>();
	private String[] tokenList;

	/**
	 * Loads the file with the grammar, non-terminal descriptions and 
	 * 1) stores the nonterminals, start variable, rules, and token conversions
	 * 2) expands rules
	 * 3) removes left recursion
	 * 4) removes left factoring
	 * 
	 * @param inputFile File to load
	 */
	public void load(File inputFile) {
		try {
			in = new BufferedReader(new FileReader(inputFile));
			String tokenString = in.readLine();
			tokenList = tokenString.substring(8, tokenString.length()).split(" ");

			String nonTerminalsRaw = in.readLine();
			nonTerminalsList = nonTerminalsRaw.substring(15, nonTerminalsRaw.length()).split(" ");
			String startRaw = in.readLine();
			start = startRaw.substring(7, startRaw.length());
			in.readLine(); //throwing away %Rules
			
			/* TODO change where logic */
			while (true) {
				String ruleText = in.readLine();
				if (ruleText == null || "where".equals(ruleText)) break;
				Rule rule = new Rule(ruleText);
				rulesList.add(rule);
			}

			expandRules();
			removeLeftRecursion();
			leftFactor();
			in.close();

		} catch (FileNotFoundException e) {
			System.err.println("Couldn't find input file");
			e.printStackTrace();
		} catch (IOException e) {
			System.err.println("Input Output Error");
			e.printStackTrace();
		} 
	}

	/**
	 * Helper method to expand rules of the form name : a | b to name : a and name : b
	 */
	public void expandRules() {
		ArrayList<Rule> rulesToAdd = new ArrayList<Rule>();
		ArrayList<Rule> rulesToRemove = new ArrayList<Rule>();
		for (Rule e: rulesList) {
			if (e.getBody().contains("|")) {
				rulesToRemove.add(e);
				String delim = "[|]";
				String[] newRules = e.getBody().split(delim);
				for (int i = 0; i < newRules.length; i++) {
					Rule rule = new Rule();
					rule.setTitle(e.getTitle());
					rule.setBody(newRules[i].trim());
					rulesToAdd.add(rule);
				}
			}
		}
		for (Rule e: rulesToAdd) rulesList.add(e);
		for (Rule e: rulesToRemove) rulesList.remove(e);
	}
	
	/**
	 * Runs through all the rules, finds left recursion, and removes it 
	 */
	public void removeLeftRecursion() {
		try {
			ArrayList<Rule> rulesToAdd = new ArrayList<Rule>();
			ArrayList<Rule> rulesToRemove = new ArrayList<Rule>();
			for (Rule e: rulesList) {
				if (e.getElement(0).equals(e.getTitle())) {
					for (Rule f: rulesList) {
						if (f.getTitle().equals(e.getTitle()) && !f.getElement(0).equals(e.getTitle())) {
							
							Rule newRule = new Rule();
							Rule newRuleEpsilon = new Rule();
							e.setElement(0, "");
							String alpha = e.getBody().trim();
							if (alpha.equals("EMPTYSTRING") || alpha == null || alpha.equals("")) continue;
							String beta = f.getElement(0);
							newRule.setTitle(e.getTitle().substring(0, e.getTitle().length() - 1) + "'>");
							newRuleEpsilon.setTitle(e.getTitle().substring(0, e.getTitle().length() - 1) + "'>");
							newRuleEpsilon.setBody("EMPTYSTRING");
							newRule.setBody(alpha + " " + newRuleEpsilon.getTitle());
							e.setBody(beta + " " + newRuleEpsilon.getTitle());
							rulesToAdd.add(newRule);
							rulesToAdd.add(newRuleEpsilon);
							
							f.setBody("");
							f.setTitle("");
							rulesToRemove.add(f);
							break;
						}	
					}
				}
			}
			for (Rule e: rulesToAdd) rulesList.add(e);
			for (Rule e: rulesToRemove) rulesList.remove(e);
			
		} catch (Exception e) {
			e.printStackTrace(System.err);
		}
	}
	
	/**
	 * Fixes rules of the type a = u v | u w (i.e., rules with common prefixes)
	 */
	public void leftFactor() {
		ArrayList<Rule> rulesToAdd = new ArrayList<Rule>();
		for (Rule e: rulesList) {
			for (Rule f: rulesList) {
				if (f.getElement(0).equals(e.getElement(0)) && f.getTitle().equals(e.getTitle()) && !f.equals(e)) {
					String u = e.getElement(0);
					e.setElement(0, "");
					f.setElement(0, "");
					String v = e.getBody().trim();
					if (v.isEmpty()) v = "EMPTYSTRING";
					String w = f.getBody().trim();
					if (w.isEmpty()) w = "EMPTYSTRING";
					e.setBody((u + " " + e.getTitle().substring(0, e.getTitle().length() - 1) + "'>").trim());
					f.setBody(v);
					f.setTitle((e.getTitle().substring(0, e.getTitle().length() - 1) + "'>").trim());
					
					Rule newRule = new Rule();
					newRule.setTitle(f.getTitle());
					newRule.setBody(w);
					rulesToAdd.add(newRule);
				}
			}
		} 
		for (Rule e: rulesToAdd) {
			rulesList.add(e);
		}
	}
	
	/**
	 * 
	 * @param args --inputFile
	 * 		args[0] = Grammar File
	 * 				 The file that has all of the rules
	 * 		args[1] = Language File
	 * 				 The file to parse
	 */
	public static void main(String[] args) {
		if(args.length != ARGC) {
			String usage = String.format("Usage:" +
					"%nprogramName GrammarFile LanguageFile" +
					"%nGrammar File -- The file that has all of the rules " +
					"%nLanguageFile -- The file to parse" +
					"%n");
			System.out.println(usage);
			return;
		}
		
		String grammarFile = args[0];
		String LanguageFile = args[1];
		Run run = new Run();
		run.load(new File(grammarFile));
		Lexer lexer = new Lexer(new File(LanguageFile), run.getTokenList());
		lexer.retrieveTokens();
		if(lexer.getTokens() == null) {
			System.err.println("Error lexering the input stream");
			return;
		}
		Parser parser = new Parser();
		Map<String, Set<String>> firstSet = parser.getFirstSet(run.getRulesList());
		Map<String, Set<String>> followSet = parser.getFollowSet(run.getRulesList(), run.getStart());
		Map<String, Rule> parseTable = parser.getParseTable(firstSet, followSet, run.getRulesList());
		System.out.printf("/** THE PARSE TABLE **/%n");
		parser.printTable(parseTable);
		parser.parse(parseTable, lexer.getTokens(), run.getStart());
	}
	
	public BufferedReader getIn() {
		return in;
	}

	public void setIn(BufferedReader in) {
		this.in = in;
	}

	public String[] getNonTerminalsList() {
		return nonTerminalsList;
	}

	public void setNonTerminalsList(String[] nonTerminalsList) {
		this.nonTerminalsList = nonTerminalsList;
	}

	public String getStart() {
		return start;
	}

	public void setStart(String start) {
		this.start = start;
	}

	public ArrayList<Rule> getRulesList() {
		return rulesList;
	}

	public void setRulesList(ArrayList<Rule> rulesList) {
		this.rulesList = rulesList;
	}
	
	public String[] getTokenList() {
		return tokenList;
	}

	public void setTokenList(String[] tokenList) {
		this.tokenList = tokenList;
	}
}
